using System;
using System.Linq;

namespace Tycho.Parser.Peg {
    public class ProductionParser : IProductionParser {
        private readonly IProduction Production;
        private readonly IDynamicGrammar DynamicGrammar;
        private readonly IParseEnvironmentFactory ParseEnvironmentFactory;
        private readonly IParseContextFactory ParseContextFactory;

        public ProductionParser (IProduction production) : this (production, null) {}

        public ProductionParser (IProduction production, IDynamicGrammar dynamicGrammar) : this (production, dynamicGrammar, new ParseEnvironmentFactory (), new ParseContextFactory ()) {}

        public ProductionParser (IProduction production, IDynamicGrammar dynamicGrammar, IParseEnvironmentFactory parseEnvironmentFactory, IParseContextFactory parseContextFactory) {
            Production = production;
            DynamicGrammar = dynamicGrammar;
            ParseEnvironmentFactory = parseEnvironmentFactory;
            ParseContextFactory = parseContextFactory;
        }

        public ITerm Parse (string source, string filename) {
            ParseEnvironment environment = ParseEnvironmentFactory.CreateWithFilename (filename);
            var parseContext = ParseContextFactory.CreateWithDynamicGrammar (DynamicGrammar);

            var sourceChars = source.ToCharArray ();
            bool thereAreSyntaxErrors = false;

            do {
                ParseResult result;
                try {
                    result = Production.Parse (
                        sourceChars,
                        0,
                        parseContext,
                        source,
                        environment);
                } catch (SyntaxErrorRecoveryException recovery) {
                    result = recovery.Result;
                }

                if (result != null) {
                    int endIndex = Whitespace.SkipWhitespaceAndNewLines (sourceChars, result.Index);

                    if (endIndex == sourceChars.Length) {
                        if (thereAreSyntaxErrors) {
                            throw new SyntaxException (result.Term, environment.SyntaxErrors.SyntaxErrors);
                        } else {
                            return result.Term;
                        }
                    } else if (!environment.SyntaxErrors.HasLoggedErrors) {
                        SourceInformation sinfo = environment.SourceFileInformation.CreateSourceInformation (result.Index, source.Length - result.Index);
                        var errorTerm = new ErrorTerm (null, sinfo);
                        throw new SyntaxException (errorTerm, new [] {new SyntaxError {Index = result.Index, Term = errorTerm, ExpectedProductions = new [] {new EndOfFileErrorInformation ()}}});
                    }
                }

                sourceChars = source.ToCharArray ();
                environment.SyntaxErrors.SetErrorInMemoTable (sourceChars);
                environment.SyntaxErrors.BeginRecovery ();
                environment.SyntaxErrorRecoverer.BeginRecovery ();

                thereAreSyntaxErrors = true;
            } while (true);
        }
    }
}